﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using StockFinder.Model;
using StockFinder.Model.Enumerations;
using System.Net;
using System.IO;
using System.Diagnostics;
using System.Threading;
using log4net;
using StockFinder.Network;

namespace StockFinder.Services.Extractors.Yahoo
{
    public class YahooHistoricPriceExtractor : IHistoricPricesExtractor
    {
        private static readonly ILog _Log = LogManager.GetLogger(typeof(YahooHistoricPriceExtractor));

        /*
         * 0. symbol
         * 1. date to MONTH - 1 
         * 2. date to DAY
         * 3. date to YEAR
         * 4. date from MONTH - 1
         * 5. date from DAY
         * 6. date from YEAR
         * */
        const string BASE_URL = "http://ichart.finance.yahoo.com/table.csv?s={0}&amp;d={1}&amp;e={2}&amp;f={3}&amp;g=d&amp;a={4}&amp;b={5}&amp;c={6}&amp;ignore=.csv";

        const string DOWNLOAD_SPREADSHEET_PATTERN = "<a href=\"([\\w\\./:\\?&;=]+)\">Download To Spreadsheet</a>";

        const string PAGE_NOT_EXIST = "Sorry, the page you requested was not found";

        private static char[] DELIMITER_COMMA = new char[] { ',' };
        private static char[] DELIMITER_NEWLINE = new char[] { '\n' };

        public HistoricPricesExtract GetHistoricPrices(string symbol)
        {
            return ExtractHistoricPrices(DateTime.Now.AddYears(-1).Date, DateTime.Now.Date, symbol);
        }

        /// <summary>
        /// Loads the historic prices into the specified symbol
        /// </summary>
        /// <param name="fromDate"></param>
        /// <param name="toDate"></param>
        /// <param name="symbol"></param>
        /// <returns></returns>
        public List<DailyPrice> GetHistoricPrices(DateTime fromDate, DateTime toDate, string symbolName)
        {
            List<DailyPrice> prices = null;

            bool success = false;

            //create the url
            string pricesUrl = string.Format(
                BASE_URL,
                symbolName,
                toDate.Month - 1,
                toDate.Day,
                toDate.Year,
                fromDate.Month - 1,
                fromDate.Day,
                fromDate.Year);

            string pageSource = null;

            using (ProxiedWebClient webClient = new ProxiedWebClient())
            {
                try
                {
                    pageSource = webClient.DownloadString(pricesUrl);

                    success = true;
                }
                catch (Exception exception)
                {
                    _Log.ErrorFormat("Error reading webpage source for '{0}'. {1}", symbolName, exception.ToString());
                }
            }

            //was the page hit a success?
            if (success)
            {
                /* check that prices exist for this symbol */

                if (string.IsNullOrEmpty(pageSource) || pageSource.IndexOf(PAGE_NOT_EXIST) > -1)
                {
                    _Log.WarnFormat("No prices exist");
                }
                else
                {
                    //does so extract prices
                    prices = ExtractDailyPricesFromSource(pageSource);
                }
            }

            return prices;
        }

        /// <summary>
        /// Loads the historic prices into the specified symbol
        /// </summary>
        /// <param name="fromDate"></param>
        /// <param name="toDate"></param>
        /// <param name="symbol"></param>
        /// <returns></returns>
        public bool LoadHistoricPrices(DateTime fromDate, DateTime toDate, string symbolName, List<DailyPrice> dailyPriceTransporter)
        {
            bool success = false;

            //create the url
            string pricesUrl = string.Format(
                BASE_URL,
                symbolName,
                toDate.Month - 1,
                toDate.Day,
                toDate.Year,
                fromDate.Month - 1,
                fromDate.Day,
                fromDate.Year);

            string pageSource = null;

            using (ProxiedWebClient webClient = new ProxiedWebClient())
            {
                try
                {
                    pageSource = webClient.DownloadString(pricesUrl);

                    success = true;
                }
                catch (Exception exception)
                {
                    _Log.ErrorFormat("Error reading webpage source for '{0}'. {1}", symbolName, exception.ToString());
                }
            } 

            //was the page hit a success?
            if (success)
            {
                /* check that prices exist for this symbol */

                if (string.IsNullOrEmpty(pageSource) || pageSource.IndexOf(PAGE_NOT_EXIST) > -1)
                {
                    _Log.WarnFormat("No prices exist");
                }
                else
                {
                    //does so extract prices
                    ExtractDailyPricesFromSourceIntoSymbol(pageSource, dailyPriceTransporter);
                }
            }

            return success;                 
        }

        private static void ExtractDailyPricesFromSourceIntoSymbol(string pageSource, List<DailyPrice> dailyPriceTransporter)
        {
            if (string.IsNullOrEmpty(pageSource))
            {
                //empty page source
                throw new ArgumentNullException("pageSource can not be null or empty in this context");
            }

            string[] dailyPriceLines = pageSource.Split(DELIMITER_NEWLINE);

            int priceCount = 0;

            //check we have some prices
            if (dailyPriceLines.Length > 1)
            {
                string[] dailyPriceComponents = null;

                string dailyPriceLine = null;

                _Log.InfoFormat("Adding prices");

                DailyPrice dailyPrice = null;

                //loop each price line
                for (int dailyPriceLineIndex = 1; dailyPriceLineIndex < dailyPriceLines.Length; dailyPriceLineIndex++)
                {
                    dailyPriceLine = dailyPriceLines[dailyPriceLineIndex];

                    //split up line into price components e.g open, close etc
                    dailyPriceComponents = dailyPriceLine.Split(DELIMITER_COMMA);

                    //make sure we have all componets
                    if (dailyPriceComponents.Length == 7)
                    {
                        //get reference
                        dailyPrice = dailyPriceTransporter[dailyPriceLineIndex - 1];
                        dailyPrice.IsSet = true;

                        dailyPrice.PriceDate = Convert.ToDateTime(dailyPriceComponents[0]);
                        dailyPrice.Open = Convert.ToDecimal(dailyPriceComponents[1]);
                        dailyPrice.High = Convert.ToDecimal(dailyPriceComponents[2]);
                        dailyPrice.Low = Convert.ToDecimal(dailyPriceComponents[3]);
                        dailyPrice.Close = Convert.ToDecimal(dailyPriceComponents[4]);
                        dailyPrice.Volume = Convert.ToInt64(dailyPriceComponents[5]);
                        dailyPrice.AdjustedClose = Convert.ToDecimal(dailyPriceComponents[6]);

                        priceCount++;
                    }

                    //reset references
                    dailyPriceLine = null;
                    dailyPriceComponents = null;
                }
            }

            _Log.InfoFormat("Added {0} prices", priceCount);
        }

        public HistoricPricesExtract ExtractHistoricPrices(DateTime fromDate, DateTime toDate, string symbol)
        {
           _Log.DebugFormat("GetHistoricPrices: {0} from {1} to {2} in Thread {3}", symbol, fromDate, toDate, Thread.CurrentThread.ManagedThreadId);

            HistoricPricesExtract extraction = new HistoricPricesExtract();

            //create the url
            string pricesUrl = string.Format(
                BASE_URL,
                symbol,
                toDate.Month - 1,
                toDate.Day,
                toDate.Year,
                fromDate.Month - 1,
                fromDate.Day,
                fromDate.Year);

            //now get the spreadsheet
            HttpWebRequest webRequest = HttpWebRequest.Create(pricesUrl) as HttpWebRequest;

            string pageSource = null;

            try
            {
                using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse())
                {
                    //got a response
                    extraction.HistoricPricesExtractResult = HistoricPricesExtractResult.SUCCESS;

                    using (StreamReader streamReader = new StreamReader(webResponse.GetResponseStream()))
                    {
                        try
                        {
                            pageSource = streamReader.ReadToEnd();
                        }
                        catch (Exception exception)
                        {
                            //something happened
                            extraction.HistoricPricesExtractResult = HistoricPricesExtractResult.ERROR;
                            extraction.Message = exception.ToString();
                            _Log.ErrorFormat("Error reading webpage source for '{0}'. {1}", symbol, exception.ToString());
                        }
                    }
                }                    
            }
            catch (WebException webException)
            {
                extraction.Message = webException.Status.ToString();
                extraction.HistoricPricesExtractResult = HistoricPricesExtractResult.ERROR;
                _Log.ErrorFormat("Error getting web page for '{0}'. {1}", symbol, webException.ToString());
            }

            //was the page hit a success?
            if (extraction.HistoricPricesExtractResult == HistoricPricesExtractResult.SUCCESS)
            {               
                /* check that prices exist for this symbol */

                if (string.IsNullOrEmpty(pageSource) || pageSource.IndexOf(PAGE_NOT_EXIST) > -1)
                {
                    //doesnt exist so quit
                    extraction.HistoricPricesExtractResult = HistoricPricesExtractResult.PRICES_NOT_EXIST;
                }
                else
                {
                    //does so extract prices
                    extraction.DailyPrices = ExtractDailyPricesFromSource(pageSource);

                    //update the dailyprice
                    //extraction.DailyPrices.ForEach(price => price.SymbolId = symbol);
                }
            }

            //make sure this reference is cleaned up
            webRequest = null;

            return extraction;
        }        

        private static List<DailyPrice> ExtractDailyPricesFromSource(string pageSource)
        {
            if (string.IsNullOrEmpty(pageSource))
            {
                //empty page source
                throw new ArgumentNullException("pageSource can not be null or empty in this context");
            }

            List<DailyPrice> dailyPrices = new List<DailyPrice>();

            string[] dailyPriceLines = pageSource.Split(DELIMITER_NEWLINE);

            //check we have some prices
            if (dailyPriceLines.Length > 1)
            {
                string[] dailyPriceComponents = null;

                string dailyPriceLine = null;

                //loop each price line
                for (int dailyPriceLineIndex = 1; dailyPriceLineIndex < dailyPriceLines.Length; dailyPriceLineIndex++)
                {
                    dailyPriceLine = dailyPriceLines[dailyPriceLineIndex];

                    //split up line into price components e.g open, close etc
                    dailyPriceComponents = dailyPriceLine.Split(DELIMITER_COMMA);

                    //make sure we have all componets
                    if (dailyPriceComponents.Length == 7)
                    {
                        var priceDate = Convert.ToDateTime(dailyPriceComponents[0]);

                        //make sure there isnt already a DailyPrice with the same date
                        if (!dailyPrices.Exists(i => i.PriceDate == priceDate))
                        {
                            //add to list
                            dailyPrices.Add(new DailyPrice()
                            {
                                PriceDate = priceDate,
                                Open = Convert.ToDecimal(dailyPriceComponents[1]),
                                High = Convert.ToDecimal(dailyPriceComponents[2]),
                                Low = Convert.ToDecimal(dailyPriceComponents[3]),
                                Close = Convert.ToDecimal(dailyPriceComponents[4]),
                                Volume = Convert.ToInt64(dailyPriceComponents[5]),
                                AdjustedClose = Convert.ToDecimal(dailyPriceComponents[6]),
                            });
                        }
                        else
                        {
                            _Log.WarnFormat("PriceDate {0} already exists, ignoring", priceDate);
                        }
                    }
                    
                    //reset references
                    dailyPriceLine = null;
                    dailyPriceComponents = null;
                }
            }

            return dailyPrices;
        }
    }
}
